package de.projectgoodmorning.alarm;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.util.SparseArray;
import de.projectgoodmorning.alarm.model.generic.Alarm;

/**
 * Der AlarmController ist fuer die Registrierung der Alarme beim Android-OS
 * zustaendig. Ebenfalls koennen bereits registrierte Alarme wieder entfernt werden.
 * 
 * Der AlarmController bildet die Schnittstelle zum Android AlarmManager. 
 * Die Besonderheit ist dass der AlarmController mit Alarm-Objekt umgehen kann.
 * 
 * Diese Klasse wird als Service behandelt. Dies gewaehrleistet eine garantierte
 * Alarmbehandlung, auch wenn die Anwendung bereits durch das System gestoppt wurde.
 */
public class AlarmController extends Service {
	private static final String TAG = AlarmController.class.getName();
	
	/** Flag zur Uebergabe von Alarm-IDs ueber Intent-Extras */
	public static final String ALARM_ID = "alarm-id";
	
	private SparseArray<Alarm> alarms;
	private Context ctx;
	private static Runnable callbackRunnable;
	
	@Override
	public void onCreate() {
		super.onCreate();
		ctx = getApplicationContext();
	}

	public AlarmController() {
		alarms = new SparseArray<Alarm>();
	}
	
	/**
	 * registriert den uebergebenen Alarm beim System.
	 * Der definierte Zeitpunkt des Alarms wird als Zeitpunkt verwendet.
	 * Sollte der Zeitpunkt in der Vergangenheit liegen, wird der Alarm
	 * nicht beim System registriert.
	 * <br>
	 * <b>WICHTIG:</b>
	 * Es ist notwendig, dass der Alarm eine eindeutige ID besitzt.
	 * Sollte dies nicht gewaehrleistet sein, kann es zum ueberschreiben eines
	 * eventuell bereits vorhanden Alarms kommen.
	 * <br>
	 * Wenn dieser Zeitpunkt erreicht ist, wird der Alarm im AlarmReceiver
	 * weiter verarbeitet.
	 * 
	 * @param alarm		erwartet ein Alarm-Objekt, sollte nicht null sein
	 */
	public void addAlarm( Alarm alarm ) {
		registerAlarm( alarm );
	}
	
	/**
	 * registriert alle uebergebenen Alarm beim System.
	 * @see AlarmController#addAlarm(Alarm)
	 * @param alarms	erwartet ein Alarm-Objekt, sollte nicht null sein
	 */
	public void addAlarms( Alarm[] alarms ) {
		for( Alarm a : alarms ) {
			registerAlarm( a );
		}
	}
	
	/**
	 * entfernt einen bereits registrierten Alarm beim System.
	 * <br>
	 * <b>WICHTIG:</b>
	 * Der registrierte Alarm wird anhand der ID des uebergebenen Alarms ermittelt.
	 * 
	 * @param alarm		erwartet einen Alarm mit gesetzter ID
	 */
	public void removeAlarm( Alarm alarm ) {
		if( alarm != null ) {
			unregisterAlarm( alarm );
		}
	}
	
	/**
	 * entfernt alle bereits registrierten Alarme beim System.
	 * @see AlarmController#removeAlarm(Alarm)
	 * @param alarm		erwartet einen Alarm mit gesetzter ID
	 */
	public void removeAlarms( Alarm[] alarms ) {
		for( Alarm a : alarms ) {
			unregisterAlarm( a );
		}
	}
	
	/**
	 * entfernt einen bereits registrierten Alarm beim System anhand der ID.
	 * @see AlarmController#removeAlarm(Alarm)
	 * @param id	erwartet die ID eines registrieten Alarms 
	 */
	public void removeById( int id ) {
		Alarm a = alarms.get( id );
		removeAlarm( a );
	}
	
	/**
	 * gibt einen registrierten Alarm anhand seine ID zurueck.
	 * 
	 * @param id	erwartet die ID eines registrieten Alarms 
	 * @return		gibt Alarm zurueck falls er gefunden wird, sonst null
	 */
	public Alarm getById( int id ) {
		return alarms.get( id );
	}
	
	/**
	 * entfernt alle registrierten Alarme.
	 */
	public void removeAllAlarms() {
		for( int i=0; i<alarms.size(); i++ ) {
			Alarm a = alarms.get( alarms.keyAt( i ) );
			unregisterAlarm( a );
		}
	}
	
	
	private void unregisterAlarm( Alarm alarm ) {
		int id = alarm.id;
		if( alarms.get( id ) != null ) {
			alarms.remove( id );
			
			alarm.state = Alarm.INACTIVE;
			
			Intent i2 = new Intent( ctx, AlarmController.class );
			i2.putExtra( ALARM_ID, alarm.id );
			PendingIntent pi2 = PendingIntent.getService( 
					ctx, alarm.id, i2, PendingIntent.FLAG_UPDATE_CURRENT );
			
			AlarmManager alarmManager = ( AlarmManager ) ctx.getSystemService( Context.ALARM_SERVICE );
			alarmManager.cancel( pi2 );

			Log.i( TAG, "Alarm " + id + " unregistered"  );
		}
	}
	
	private void registerAlarm( Alarm alarm ) {
		if( alarm == null ) {
			Log.w( TAG, "alarm is null: object skipped" );
			return;
		}
		
		Calendar now = Calendar.getInstance();
		if( alarm.getMoment() < now.getTimeInMillis() ) {
			Log.w( TAG, "Alarm is in the past: object skipped" );
			return;
		}
		
		alarms.put( alarm.id, alarm );

		Intent i2 = new Intent( ctx, AlarmController.class );
		i2.putExtra( ALARM_ID, alarm.id );
		PendingIntent pi2 = PendingIntent.getService( 
				ctx, alarm.id, i2, PendingIntent.FLAG_UPDATE_CURRENT );
		
		AlarmManager alarmManager = ( AlarmManager ) ctx.getSystemService( Context.ALARM_SERVICE );
		alarmManager.set( AlarmManager.RTC_WAKEUP, alarm.getMoment(), pi2 );		
		alarm.state = Alarm.ACTIVE;
		
		SimpleDateFormat sdf = new SimpleDateFormat( "dd.MM.yyyy - HH:mm:ss", Locale.getDefault() );
		Calendar cal = Calendar.getInstance();
		cal.setTimeInMillis( alarm.getMoment() );
		Log.i( TAG, "Alarm " + alarm.id + " registered for " + sdf.format( cal.getTime() ) );
	}
	
	@Override
	public void onStart( Intent intent, int startId ) {
		super.onStart( intent, startId );
		
		Bundle extras = intent.getExtras();
		
		if( extras != null && extras.containsKey( ALARM_ID ) ) {
			Alarm a = getById( extras.getInt( ALARM_ID ) );
			if( a != null ) {
				a.execute( getApplicationContext() );
				Log.d( TAG, "Alarm executed - ID: " + a.id );

				a.state = Alarm.INACTIVE;
				removeById( a.id );
			} else {
				Log.e( TAG, "Alarm not found - ID: " + extras.getInt( ALARM_ID ) );
			}
			
			if( callbackRunnable != null ) {
				callbackRunnable.run();
			}
		}
	}
	
	/**
	 * legt einen Listener fest, welcher nach der Ausfuehrung eines Alarms
	 * ausgefueht wird.
	 * @param callback		erwartet eine Callback-Routine
	 */
	public static void setOnExecuteListener( Runnable callback ) {
		callbackRunnable = callback;
	}
	
	/////////////////////// SERVICE ///////////////////////////
	
    public class LocalBinder extends Binder {
    	public AlarmController getService() {
            return AlarmController.this;
        }
    }		
	
	private final IBinder binder = new LocalBinder();
	@Override
	public IBinder onBind( Intent intent ) {
		return binder;
	}
}